Skip to content

H2: separate feed status evidence from producer result - #45

Closed
Pigbibi wants to merge 1 commit into
mainfrom
feat/pert-upstream-feed-status-layered-result-h2
Closed

H2: separate feed status evidence from producer result#45
Pigbibi wants to merge 1 commit into
mainfrom
feat/pert-upstream-feed-status-layered-result-h2

Conversation

@Pigbibi

@Pigbibi Pigbibi commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Scope

Fresh H2 from latest origin/main@294e30b8eed36f070299b1dc01d34955fb262918; PR #44 remains permanently frozen evidence and was not reused.

This producer-side slice separates canonical status evidence from the producer result:

  • all-quarantined feeds produce canonical incomplete/non-eligible status and a non-hard-failure quarantine result;
  • any failed feed (including failed+quarantined or accepted+failed) writes canonical incomplete evidence first, then hard-fails the producer result;
  • accepted+quarantined returns rows but remains incomplete/non-eligible;
  • empty configured feed input is invalid;
  • per-feed state/kind/rows/error/digest invariants are validated from one snapshot, and BaseException is not swallowed;
  • explicit RSS2/Atom kind and zero-entry quarantine are preserved.

No workflow, publish, weekly, QAR, permission, or automation changes are included.

Validation

  • focused pytest: 25 passed
  • full pytest: 133 passed
  • compileall: passed
  • git diff --check: passed
  • ruff: unavailable in environment

Co-Authored-By: Codex <noreply@openai.com>
@github-actions

Copy link
Copy Markdown
Contributor

🤖 Codex PR Review

🚫 Merge blocked: 2 serious issue(s) found in high-risk files

🚫 Blocking Issues

These issues must be fixed before this PR can be merged:

1. 🟠 [HIGH] Logic in src/political_event_tracking_research/rss_source_fetch.py

When continue_on_feed_error is false, the function re-raises inside the per-feed except block before reaching the later write_fetch_status(...) call. That means the new canonical status evidence is never written on the default fail-fast path, which defeats the PR’s goal of separating feed-status evidence from the producer result whenever any feed fails. (line 307)

Suggestion: Accumulate the failed record, exit the loop, write status_output from the collected feed_records, and only then raise the hard-failure exception. A finally/post-loop write path is the safest way to guarantee evidence emission.

2. 🟠 [HIGH] Bug in src/political_event_tracking_research/rss_source_fetch.py

parse_feed_snapshot now rejects any RSS channel child or Atom feed child that is not in a small hard-coded allowlist. Real-world feeds commonly include legal extension metadata (for example atom:link, sy:updatePeriod, Media RSS, Dublin Core, custom namespaces), so valid feeds that previously parsed will now fail with feed_schema_invalid and be treated as failed feeds. (line 216)

Suggestion: Do not hard-fail on unknown top-level metadata/extension elements. Validate only the required structural container (rss/channel or Atom feed/entry) and ignore unrecognized extension tags unless they break the specific fields this parser actually consumes.

ℹ️ Other Findings

1. 🟡 [MEDIUM] Reliability in src/political_event_tracking_research/feed_primitives.py

build_fetch_result materializes records, then calls build_status(records) and reparses the same mappings again with _record(record) to build rows. For non-stable Mapping inputs, the second parse can observe different state/rows values than the first, producing a FetchResult whose status, rows, and hard_failure fields do not describe the same snapshot. (line 184)

Suggestion: Parse and validate the feed records once, keep that parsed snapshot, and derive both the returned status and the returned rows from the same immutable parsed structure.


Review by Codex PR Review bot • PR

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c3b502a582

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

if statuses and not any(item.ok for item in statuses):
if not feed_records:
raise ValueError("feed_config_empty")
if any(record["state"] == "failed" for record in feed_records):

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Gate quarantined snapshots before publishing live outputs

When at least one feed is quarantined and none failed, this condition is false, so fetch_rss_sources exits 0 after writing a status with eligible_for_live_publication=false. I checked .github/workflows/rss_source_pipeline.yml: the publish step only depends on the command succeeding and copies data/output/rss_source_pipeline/source_fetch_status.json into data/live, and repo search shows no workflow check for eligible_for_live_publication; a zero-entry feed can therefore still publish live source_items/source_events that the new status marks non-eligible. Either fail this path for quarantines in the scheduled publisher or add a workflow gate before copying live outputs.

Useful? React with 👍 / 👎.

item_count=0,
error=f"{type(exc).__name__}: {exc}",
)
code = getattr(exc, "code", "fetch_failed")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve XML failure codes in status evidence

When fetch_url or parse_feed_snapshot raises FeedXmlError("feed_xml_oversize") or FeedXmlError("feed_schema_invalid") under --continue-on-feed-error, FeedXmlError has no code attribute, so this falls back to fetch_failed and the canonical status loses the sanitized root cause. Since those XML errors are already producer-boundary codes and tests distinguish them at the exception boundary, use the exception argument or give FeedXmlError a code attribute; otherwise operators cannot tell bad/oversized payloads from network fetch failures in the new status file.

Useful? React with 👍 / 👎.

Comment on lines +273 to +274
if data["configured_feed_count"] != len(feeds) or data["feed_count"] != len(feeds):
_fail("status_counter_invalid")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject empty canonical feed-status wires

For a parsed status payload with feeds=[] and all counters set to zero, this count check passes and the later complete = failed == 0 and quarantined == 0 logic accepts eligible_for_live_publication=true. That contradicts the new producer invariant that empty feed configuration is invalid (build_status([]) raises feed_config_empty), so any consumer that uses parse_status_bytes as the contract validator can accept an impossible empty snapshot as publication-ready.

Useful? React with 👍 / 👎.

if status_output:
write_fetch_status(status_output, statuses, item_count=0)
write_fetch_status(status_output, feed_records)
raise RuntimeError("all configured RSS/Atom feeds failed")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve failure evidence before raising

When a partial feed failure occurs in the inspected RSS Source Pipeline, this raise happens after writing source_fetch_status.json but before the workflow reaches the Upload RSS source artifact step, which has the default success-only behavior. The new hard-fail path therefore discards the canonical evidence file from the Actions run for the accepted+failed case that used to complete and upload artifacts; make the upload/status preservation step run on failure or defer the raise until after evidence is externally retained.

Useful? React with 👍 / 👎.

Comment on lines +228 to +229
if any(child.tag not in allowed and child.tag not in _RSS_FOREIGN_METADATA for child in channel):
raise FeedXmlError("feed_schema_invalid")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Allow standard RSS extension metadata

For RSS 2.0 feeds that put namespaced syndication metadata directly under <channel> (for example sy:updatePeriod or sy:updateFrequency), this rejects the entire feed before item extraction even though the previous parser ignored unknown channel children and still parsed <item> rows. Since feed configs are operator-provided and these extension elements are common on otherwise valid RSS feeds, a single source with such metadata becomes a failed feed and now aborts the run under the new any-failed behavior.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant